﻿using Aurora.Discovery.HttpClient.Abstractions;
using HttpClient.Options;
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.CircuitBreaker;
using Polly.Fallback;
using Polly.Retry;
using Polly.Timeout;

namespace Aurora.HttpClient
{
    public static class ServiceCollectionExtensions
    {
        /// <summary>
        /// 添加httpclient服务
        /// </summary>
        /// <param name="services"></param>
        /// <param name="setupAction">配置参数</param>
        public static void AddCoreHttpClient(this IServiceCollection services, Action<HttpClientOptions> setupAction)
        {
            if (setupAction == null) throw new ArgumentNullException(nameof(setupAction));

            IHttpClientBuilder builder = services.AddHttpClient<IHttpClient, DefaultHttpClient>();
            HttpClientOptions options = new HttpClientOptions();
            setupAction(options);
            services.Configure(setupAction);
            if (options.UsePolicy)
            {
                builder.AddPolicyHandler(GetCircuitBreakerPolicyAsync(options.ExceptionCountAllowedBeforeBreaking,
                    options.Seconds));
                builder.AddPolicyHandler(GetRetryPolicyAsync(options.RetryCount));
                builder.AddPolicyHandler(GetTimeoutPolicyAsync(options.TimeoutSecond));
            }

            if (options.UseDiscovery)
            {
                builder.AddHttpClientDiscovery();
                services.AddDiscovery(options.DiscoveryAction);
            }

            if (options.UseHeaderPropagation)
            {
                builder.AddHeaderPropagation();
                if (options.HeaderPropagations?.Count > 0)
                {
                    services.AddHeaderPropagation(opt =>
                    {
                        foreach (var header in options.HeaderPropagations)
                        {
                            opt.Headers.Add(header);
                        }
                    });
                }
            }
        }

        /// <summary>
        /// 降级策略
        /// </summary>
        /// <returns></returns>
        private static AsyncFallbackPolicy<HttpResponseMessage> GetFallbackPolicyAsync()
        {
            var fallbackResponse = new HttpResponseMessage
            {
                Content = new StringContent("fallback"),
                StatusCode = System.Net.HttpStatusCode.BadGateway
            };

            var fallbackPolicy = Policy<HttpResponseMessage>.Handle<Exception>()
                .FallbackAsync(fallbackResponse, b =>
                {
                    Console.WriteLine($"请求降级原因【{b.Exception.Message}】");
                    return Task.CompletedTask;
                });
            return fallbackPolicy;
        }

        /// <summary>
        /// 熔断策略
        /// </summary>
        /// <remarks>下面设置的是连续出错6次之后熔断60秒，意思是：连续出错5次后，熔断60s，在这60s内，再次访问，不再执行Execute中的代码，直接报错，
        /// 60s熔断时间过后，继续访问，如果还是出错(出一次即可)，直接熔断60s， 再次重复这个过程</remarks>
        /// <returns></returns>
        private static AsyncCircuitBreakerPolicy<HttpResponseMessage> GetCircuitBreakerPolicyAsync(
            int handledEventsAllowedBeforeBreaking = 5, int second = 60)
        {
            var circuitBreakerPolicy = Policy.Handle<HttpRequestException>().Or<TimeoutException>()
                .Or<TimeoutRejectedException>()
                .OrResult<HttpResponseMessage>(res => res.StatusCode != System.Net.HttpStatusCode.OK)
                .CircuitBreakerAsync(handledEventsAllowedBeforeBreaking, TimeSpan.FromSeconds(second),
                    (ex, ts) =>
                    {
                        Console.WriteLine(ex);
                        Console.WriteLine($"请求熔断时间:{ts.TotalMilliseconds}ms");
                    },
                    () => { Console.WriteLine("请求被熔断后恢复"); });
            return circuitBreakerPolicy;
        }

        /// <summary>
        /// 重试策略
        /// </summary>
        /// <param name="retryCount">重试次数</param>
        /// <returns></returns>
        private static AsyncRetryPolicy<HttpResponseMessage> GetRetryPolicyAsync(int retryCount)
        {
            var retryPolicy = Policy.Handle<HttpRequestException>().Or<TimeoutException>()
                .Or<TimeoutRejectedException>()
                .OrResult<HttpResponseMessage>(res =>
                {
                    Console.WriteLine(res.StatusCode);
                    return res.StatusCode != System.Net.HttpStatusCode.OK;
                })
                .WaitAndRetryAsync(
                    retryCount: retryCount,
                    sleepDurationProvider: retryAttempt =>
                    {
                        var waitSeconds = TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1));
                        Console.WriteLine($"1请求重试[{retryAttempt}]--等待{waitSeconds}秒");
                        return waitSeconds;
                    });
            return retryPolicy;
        }

        /// <summary>
        /// 超时策略
        /// </summary>
        /// <param name="timeOutSecond">超时时间 秒</param>
        /// <returns></returns>
        private static AsyncTimeoutPolicy<HttpResponseMessage> GetTimeoutPolicyAsync(int timeOutSecond = 30)
        {
            var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(timeOutSecond);
            return timeoutPolicy;
        }
    }
}